﻿using System;
using System.Reflection;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;
using Microsoft.Practices.Unity.InterceptionExtension;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using vm.Aspects.Policies;
using vm.Aspects.Diagnostics;
using Microsoft.Practices.EnterpriseLibrary.Validation.PolicyInjection;

namespace vm.Aspects.Policies.Tests
{
    [TestClass]
    public class ParameterValidatingCallHandlerTest
    {
        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext { get; set; }

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        //[TestInitialize()]
        //public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion

        class A
        {
            [RangeValidator(0, RangeBoundaryType.Inclusive, 10, RangeBoundaryType.Exclusive)]
            public int I { get; set; }

            [RangeValidator(0, RangeBoundaryType.Inclusive, 10, RangeBoundaryType.Exclusive)]
            public virtual int J { get; set; }
        }

        class B : A
        {
            [RangeValidator(0, RangeBoundaryType.Inclusive, 10, RangeBoundaryType.Exclusive)]
            public override int J { get; set; }
        }

        interface ITested
        {
            int Method1(

                [RangeValidator(0, RangeBoundaryType.Inclusive, 10, RangeBoundaryType.Exclusive)]
                int i);

            int Method2(
                int i);

            int Method3(

                [NotNullValidator]
                A a);
        }

        class Tested : ITested
        {
            #region ITested Members

            public int Method1(int i)
            {
                return i;
            }

            public int Method2(

                [RangeValidator(0, RangeBoundaryType.Inclusive, 10, RangeBoundaryType.Exclusive)]
                int i)
            {
                return i;
            }

            public int Method3(A a)
            {
                return a.J;
            }

            #endregion
        }

        [ExpectedException(typeof(ArgumentNullException))]
        [TestMethod]
        public void InvokeInputNullTest()
        {
            try
            {
                var target = new ParameterValidatingCallHandler();
                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method1"),
                                    new ParameterCollection(
                                            new object[] { 1 },
                                            typeof(ITested).GetMethod("Method1").GetParameters(),
                                            p => true));

                var ret = target.Invoke(null, () => call.InvokeNext);
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [ExpectedException(typeof(ArgumentNullException))]
        [TestMethod]
        public void InvokeGetNextNullTest()
        {
            try
            {
                var target = new ParameterValidatingCallHandler();
                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method1"),
                                    new ParameterCollection(
                                            new object[] { 1 },
                                            typeof(ITested).GetMethod("Method1").GetParameters(),
                                            p => true));

                var ret = target.Invoke(call, null);
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void InvokeMethod1Test()
        {
            try
            {
                var target = new ParameterValidatingCallHandler();
                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method1"),
                                    new ParameterCollection(
                                            new object[] { 1 },
                                            typeof(ITested).GetMethod("Method1").GetParameters(),
                                            p => true));

                var ret = target.Invoke(call, () => call.InvokeNext);
                Assert.AreEqual(1, ret.ReturnValue);
                Assert.AreEqual(1, call.TimesNextCalled);
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void InvokeMethod1OutOfRangeTest()
        {
            try
            {
                var target = new ParameterValidatingCallHandler();
                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method1"),
                                    new ParameterCollection(
                                            new object[] { -1 },
                                            typeof(ITested).GetMethod("Method1").GetParameters(),
                                            p => true));

                var ret = target.Invoke(call, () => call.InvokeNext);
                Assert.AreEqual(0, call.TimesNextCalled);
                Assert.IsNotNull(ret.Exception);
                Assert.IsTrue(ret.Exception is ArgumentValidationException);
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void InvokeMethod2Test()
        {
            try
            {
                var target = new ParameterValidatingCallHandler();
                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method2"),
                                    new ParameterCollection(
                                            new object[] { 1 },
                                            typeof(ITested).GetMethod("Method2").GetParameters(),
                                            p => true));

                var ret = target.Invoke(call, () => call.InvokeNext);

                // the parameter of ITest.Method2 doesn't have validators, so the validators of Tested.Method2 are ignored.
                Assert.AreEqual(1, call.TimesNextCalled);
                Assert.AreEqual(1, ret.ReturnValue);
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void InvokeMethod2OutOfRangeTest()
        {
            try
            {
                var target = new ParameterValidatingCallHandler();
                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method2"),
                                    new ParameterCollection(
                                            new object[] { 1 },
                                            typeof(ITested).GetMethod("Method2").GetParameters(),
                                            p => true));

                var ret = target.Invoke(call, () => call.InvokeNext);

                // the parameter of ITest.Method2 doesn't have validators, so the validators of Tested.Method2 are ignored.
                Assert.AreEqual(1, call.TimesNextCalled);
                Assert.AreEqual(1, ret.ReturnValue);
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }

        [TestMethod]
        public void InvokeMethod3Test()
        {
            try
            {
                var target = new ParameterValidatingCallHandler();
                ITested tested = new Tested();
                var call = new MethodInvocationMock(
                                    tested,
                                    typeof(ITested).GetMethod("Method3"),
                                    new ParameterCollection(
                                            new object[] { null },
                                            typeof(ITested).GetMethod("Method3").GetParameters(),
                                            p => true));

                var ret = target.Invoke(call, () => call.InvokeNext);

                Assert.AreEqual(0, call.TimesNextCalled);
                Assert.IsNotNull(ret.Exception);
                Assert.IsTrue(ret.Exception is ArgumentValidationException);
            }
            catch (UnitTestAssertException)
            {
                throw;
            }
            catch (Exception x)
            {
                TestContext.WriteLine("{0}", x.DumpString());
                throw;
            }
        }
    }
}
